/******************************************************************************
 * File:   ogrdxf_polyline_smooth.cpp
 *
 * Project:  Interpolation support for smooth POLYLINE and LWPOLYLINE entities.
 * Purpose:  Implementation of classes for OGR .dxf driver.
 * Author:   TJ Snider, timsn@thtree.com
 *           Ray Gardener, Daylon Graphics Ltd.
 *
 ******************************************************************************
 * Copyright (c) 2010 Daylon Graphics Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "stdlib.h"
#include "math.h"
#include "ogrdxf_polyline_smooth.h"

/************************************************************************/
/*                Local helper functions                                */
/************************************************************************/

static double GetRadius(double bulge, double length)
{
    const double h = (bulge * length) / 2;
    return (h / 2) + (length * length / (8 * h));
}

static double GetLength(const DXFSmoothPolylineVertex &start,
                        const DXFSmoothPolylineVertex &end)
{
    return sqrt(pow(end.x - start.x, 2) + pow(end.y - start.y, 2));
}

static double GetAngle(const DXFSmoothPolylineVertex &start,
                       const DXFSmoothPolylineVertex &end)
{
    return atan2((start.y - end.y), (start.x - end.x)) * 180.0 / M_PI;
}

static double GetOGRangle(double angle)
{
    return angle > 0.0 ? -(angle - 180.0) : -(angle + 180.0);
}

/************************************************************************/
/*                DXFSmoothPolyline::Tessellate()                        */
/************************************************************************/

OGRGeometry *DXFSmoothPolyline::Tessellate() const
{
    assert(!m_vertices.empty());

    /* -------------------------------------------------------------------- */
    /*      If polyline is one vertex, convert it to a point                */
    /* -------------------------------------------------------------------- */

    if (m_vertices.size() == 1)
    {
        OGRPoint *poPt =
            new OGRPoint(m_vertices[0].x, m_vertices[0].y, m_vertices[0].z);
        if (m_vertices[0].z == 0 || m_dim == 2)
            poPt->flattenTo2D();
        return poPt;
    }

    /* -------------------------------------------------------------------- */
    /*      Otherwise, presume a line string                                */
    /* -------------------------------------------------------------------- */

    OGRLineString *poLS = new OGRLineString;

    m_blinestringstarted = false;

    std::vector<DXFSmoothPolylineVertex>::const_iterator oIter =
        m_vertices.begin();
    std::vector<DXFSmoothPolylineVertex>::const_iterator oEndIter =
        m_vertices.end();

    --oEndIter;

    DXFSmoothPolylineVertex begin = *oIter;

    while (oIter != oEndIter)
    {
        ++oIter;
        DXFSmoothPolylineVertex end = *oIter;

        const double len = GetLength(begin, end);

        // Don't bother handling bulge for non-constant Z
        if (len == 0 || begin.bulge == 0 || begin.z != end.z)
        {
            EmitLine(begin, end, poLS);
        }
        else
        {
            const double radius = GetRadius(begin.bulge, len);
            EmitArc(begin, end, radius, len, begin.bulge, poLS, begin.z);
        }

        // Move to next vertex
        begin = end;
    }

    /* -------------------------------------------------------------------- */
    /*      Flatten to 2D if necessary                                      */
    /* -------------------------------------------------------------------- */

    if (m_dim == 2)
        poLS->flattenTo2D();

/* -------------------------------------------------------------------- */
/*      If polyline is closed, convert linestring to a linear ring      */
/*                                                                      */
/*      Actually, on review I'm not convinced this is a good idea.      */
/*      Note that most (all) "filled polygons" are expressed with       */
/*      hatches which are now handled fairly well and they tend to      */
/*      echo linear polylines.                                          */
/* -------------------------------------------------------------------- */
#ifdef notdef
    if (m_bClosed)
    {
        OGRLinearRing *poLR = new OGRLinearRing();
        poLR->addSubLineString(poLS, 0);
        delete poLS;

        // Wrap as polygon.
        OGRPolygon *poPoly = new OGRPolygon();
        poPoly->addRingDirectly(poLR);

        return poPoly;
    }
#endif

    return poLS;
}

/************************************************************************/
/*                DXFSmoothPolyline::EmitArc()                        */
/************************************************************************/

void DXFSmoothPolyline::EmitArc(const DXFSmoothPolylineVertex &start,
                                const DXFSmoothPolylineVertex &end,
                                double radius, double len, double bulge,
                                OGRLineString *poLS, double dfZ) const
{
    assert(poLS);

    double ogrArcRotation = 0.0;
    const double ogrArcRadius = fabs(radius);

    /* -------------------------------------------------------------------- */
    /*      Set arc's direction and keep bulge positive                     */
    /* -------------------------------------------------------------------- */

    const bool bClockwise = (bulge < 0);

    if (bClockwise)
        bulge *= -1;

    /* -------------------------------------------------------------------- */
    /*      Get arc's center point                                          */
    /* -------------------------------------------------------------------- */

    const double saggita = fabs(bulge * (len / 2.0));
    const double apo =
        bClockwise ? -(ogrArcRadius - saggita) : -(saggita - ogrArcRadius);

    DXFSmoothPolylineVertex v;
    v.x = start.x - end.x;
    v.y = start.y - end.y;

#ifdef notdef
    const bool bMathissue = (v.x == 0.0 || v.y == 0.0);
#endif

    DXFSmoothPolylineVertex midpoint;
    midpoint.x = end.x + 0.5 * v.x;
    midpoint.y = end.y + 0.5 * v.y;

    DXFSmoothPolylineVertex pperp;
    pperp.x = v.y;
    pperp.y = -v.x;
    pperp.normalize();

    DXFSmoothPolylineVertex ogrArcCenter;
    ogrArcCenter.x = midpoint.x + (pperp.x * apo);
    ogrArcCenter.y = midpoint.y + (pperp.y * apo);

    /* -------------------------------------------------------------------- */
    /*      Get the line's general vertical direction (-1 = down, +1 = up)  */
    /* -------------------------------------------------------------------- */

    const double linedir = end.y > start.y ? 1.0 : -1.0;

    /* -------------------------------------------------------------------- */
    /*      Get arc's starting angle.                                       */
    /* -------------------------------------------------------------------- */

    double a = GetAngle(ogrArcCenter, start);

    if (bClockwise && (linedir == 1.0))
        a += (linedir * 180.0);

    double ogrArcStartAngle = GetOGRangle(a);

    /* -------------------------------------------------------------------- */
    /*      Get arc's ending angle.                                         */
    /* -------------------------------------------------------------------- */

    a = GetAngle(ogrArcCenter, end);

    if (bClockwise && (linedir == 1.0))
        a += (linedir * 180.0);

    double ogrArcEndAngle = GetOGRangle(a);

    if (!bClockwise && (ogrArcStartAngle < ogrArcEndAngle))
        ogrArcEndAngle = -180.0 + (linedir * a);

    if (bClockwise && (ogrArcStartAngle > ogrArcEndAngle))
        ogrArcEndAngle += 360.0;

    /* -------------------------------------------------------------------- */
    /*      Flip arc's rotation if necessary.                               */
    /* -------------------------------------------------------------------- */

    if (bClockwise && (linedir == 1.0))
        ogrArcRotation = linedir * 180.0;

    /* -------------------------------------------------------------------- */
    /*      Tessellate the arc segment and append to the linestring.        */
    /* -------------------------------------------------------------------- */

    if (fabs(ogrArcEndAngle - ogrArcStartAngle) <= 361.0)
    {
        OGRLineString *poArcpoLS =
            OGRGeometryFactory::approximateArcAngles(
                ogrArcCenter.x, ogrArcCenter.y, dfZ, ogrArcRadius, ogrArcRadius,
                ogrArcRotation, ogrArcStartAngle, ogrArcEndAngle, 0.0,
                m_bUseMaxGapWhenTessellatingArcs)
                ->toLineString();

        poLS->addSubLineString(poArcpoLS);

        delete poArcpoLS;
    }
    else
    {
        // TODO: emit error ?
    }
}

/************************************************************************/
/*                   DXFSmoothPolyline::EmitLine()                      */
/************************************************************************/

void DXFSmoothPolyline::EmitLine(const DXFSmoothPolylineVertex &start,
                                 const DXFSmoothPolylineVertex &end,
                                 OGRLineString *poLS) const
{
    assert(poLS);

    if (!m_blinestringstarted)
    {
        poLS->addPoint(start.x, start.y, start.z);
        m_blinestringstarted = true;
    }

    poLS->addPoint(end.x, end.y, end.z);
}

/************************************************************************/
/*                DXFSmoothPolyline::Close()                            */
/************************************************************************/

void DXFSmoothPolyline::Close()
{
    assert(!m_bClosed);

    if (m_vertices.size() >= 2)
    {
        const bool bVisuallyClosed =
            (m_vertices.back().shares_2D_pos(m_vertices[0]));

        if (!bVisuallyClosed)
        {
            m_vertices.push_back(m_vertices[0]);
        }
        m_bClosed = true;
    }
}
